/*
 * Copyright (c) United Binary LLC.  All rights reserved.
 * 
 * This code is licensed under the MIT License
 * 
 * SEE: http://harnessit.codeplex.com/license
 * 
 */
using System;
using System.Collections;
using System.ComponentModel;
using System.Runtime.CompilerServices;
using UnitedBinary.UnitTests.Framework.TestItems;

namespace UnitedBinary.UnitTests.Framework
{
	/// <include file='comments.xml' path='Docs/TestMethodRecord/Class/*'/>
	[Serializable]
	public sealed class TestMethodRecord : ITest
	{
		#region Member Variables

		// Array to hold all TestObjects generated during the test.
		private ArrayList testObjects = ArrayList.Synchronized( new ArrayList() );
		// Number of successful tests.
		private int successes = 0;
		// Stores whether tests have been executed with this TestMethodRecord.
		private bool hasRun = false;
		// Stores whether this method signaled an exception.
		private bool expectException = false;
		// Message associated with SignalException().
		private string expectedExceptionMsg = "";

		private TestExpressions testExpressions;

		private ArrayList testExceptionList = ArrayList.Synchronized( new ArrayList() );

		// Expected type of exception.
		private Type expectedType = typeof (Exception);

		#endregion

		#region Construction

		/// <include file='comments.xml' path='Docs/TestMethodRecord/ctor/*'/>
		[EditorBrowsable(EditorBrowsableState.Never)]
		public TestMethodRecord()
		{
			testExpressions = new TestExpressions( this );
		}

		#endregion

		#region Test Running Code

		/// <include file='comments.xml' path='Docs/TestMethodRecord/RunTest/*'/>
		public bool RunTest(bool assertion, string description)
		{
			HasRun = true;

			if ( description == null )
			{
				description = "";
			}

			if ( assertion )
			{
				successes++;
			}

			testObjects.Add( new TestResult( assertion, description ) );
			if (!assertion)
			{
			//	throw new FailedTestException(description);
				return false;
			}
			else
			{
				return true;
			}
		}

		/// <include file='comments.xml' path='Docs/TestMethodRecord/WriteLine/*'/>
		public void WriteLine(string message)
		{
			testObjects.Add( new TestComment( message ) );
		}

		/// <include file='comments.xml' path='Docs/TestMethodRecord/HasRun/*'/>
		[EditorBrowsable(EditorBrowsableState.Never)]
		public bool HasRun
		{
			get { return hasRun; }
			set { hasRun = value; }
		}

		/// <include file='comments.xml' path='Docs/TestMethodRecord/SignalException_string/*'/>
		[Obsolete("Please use the RegisterException method.")]
		[EditorBrowsable(EditorBrowsableState.Never)]
		public void SignalException(string description)
		{
			RegisterException( description );
		}

		/// <include file='comments.xml' path='Docs/TestMethodRecord/SignalException_string_Type/*'/>
		[Obsolete("Please use the RegisterException method.")]
		[EditorBrowsable(EditorBrowsableState.Never)]
		public void SignalException(string description, Type type)
		{
			RegisterException( description, type );
		}

		/// <include file='comments.xml' path='Docs/TestMethodRecord/RegisterException_string/*'/>
		public void RegisterException(string description)
		{
			RegisterException( description, typeof (Exception) );
		}

		/// <include file='comments.xml' path='Docs/TestMethodRecord/RegisterException_string_Type/*'/>
		[MethodImpl(MethodImplOptions.Synchronized)]
		public void RegisterException(string description, Type type)
		{
			if ( description == null )
			{
				description = "";
			}

			if ( type == null )
			{
				type = typeof (Exception);
			}

			this.expectException = true;
			this.expectedType = type;
			this.expectedExceptionMsg = description;
		}

		#endregion

		#region Result Accessing Code

		/// <include file='comments.xml' path='Docs/TestMethodRecord/TestCount/*'/>
		[EditorBrowsable(EditorBrowsableState.Never)]
		public int TestCount
		{
			get { return CountTestResults(); }
		}

		/// <include file='comments.xml' path='Docs/TestMethodRecord/TestObjectCount/*'/>
		[EditorBrowsable(EditorBrowsableState.Never)]
		public int TestObjectCount
		{
			get { return testObjects.Count; }
		}

		/// <include file='comments.xml' path='Docs/TestMethodRecord/CountTestResults/*'/>
		[EditorBrowsable(EditorBrowsableState.Never)]
		private int CountTestResults()
		{
			int c = 0;
			foreach ( TestObject o in testObjects )
			{
				if ( o.TestObjectType == TestObjectTypes.TestResult )
				{
					c++;
				}
			}
			return c;
		}

		/// <include file='comments.xml' path='Docs/TestMethodRecord/GetResult/*'/>
		[EditorBrowsable(EditorBrowsableState.Never)]
		public TestObject GetResult(int i)
		{
			return (TestObject)(testObjects[i]);
		}


		/// <include file='comments.xml' path='Docs/TestMethodRecord/Successes/*'/>
		[EditorBrowsable(EditorBrowsableState.Never)]
		public int Successes
		{
			get { return successes; }
		}

		/// <include file='comments.xml' path='Docs/TestMethodRecord/Failures/*'/>
		[EditorBrowsable(EditorBrowsableState.Never)]
		public int Failures
		{
			get { return TestCount - successes; }
		}

		/// <include file='comments.xml' path='Docs/TestMethodRecord/TE/*'/>
		[EditorBrowsable(EditorBrowsableState.Never)]
		public TestException[] TestExceptions
		{
			get
			{
				TestException[] exceptions = new TestException[testExceptionList.Count];
				testExceptionList.CopyTo( exceptions );
				return exceptions;
			}
		}

		/// <include file='comments.xml' path='Docs/TestMethodRecord/TestExceptionCount/*'/>
		[EditorBrowsable(EditorBrowsableState.Never)]
		public int TestExceptionCount
		{
			get { return testExceptionList.Count; }
		}

		/// <include file='comments.xml' path='Docs/TestMethodRecord/AddTestException/*'/>
		[EditorBrowsable(EditorBrowsableState.Never)]
		public void AddTestException(TestException te)
		{
			testExceptionList.Add( te );
		}

		/// <include file='comments.xml' path='Docs/TestMethodRecord/ExpectException/*'/>
		[EditorBrowsable(EditorBrowsableState.Never)]
		public bool ExpectException
		{
			get { return expectException; }
		}

		/// <include file='comments.xml' path='Docs/TestMethodRecord/ExpectedExceptionMessage/*'/>
		[EditorBrowsable(EditorBrowsableState.Never)]
		public string ExpectedExceptionMessage
		{
			get { return expectedExceptionMsg; }
		}

		/// <include file='comments.xml' path='Docs/TestMethodRecord/ExpectedExceptionType/*'/>
		[EditorBrowsable(EditorBrowsableState.Never)]
		public Type ExpectedExceptionType
		{
			get { return expectedType; }
		}

		/// <include file='comments.xml' path='Docs/TestMethodRecord/ConsistentException/*'/>
		[EditorBrowsable(EditorBrowsableState.Never)]
		public bool ConsistentException
		{
			get
			{
				// If there is no expected exception, this is the only test.
				if ( ExpectException == false )
				{
					return (testExceptionList.Count == 0);
				}

				// If they expect an exception and didn't get one, that's inconsistent.
				if ( ExpectException == true && testExceptionList.Count == 0 )
				{
					return false;
				}

				//
				// Iterate through the exception looking for a matching type.
				// Because of the pinvoke way of returning exceptions, we need
				// to look in the inner exceptions too. Otherwise, every exception
				// will be "Exception Thrown By Invocation Target".
				//
				//Exception inner = E;
				TestException inner = this.TestExceptions[0];
				TestExceptionType eType;
				string objectTypeString = typeof (object).ToString();
				string expectedTypeString = this.expectedType.ToString();
				while ( inner != null )
				{
					// Compare types and all base types of inner.
					eType = inner.Type;
					while ( eType.ExceptionType != objectTypeString )
					{
						if ( eType.ExceptionType == expectedTypeString )
						{
							return true;
						}
						eType = eType.ExceptionBaseType;
					}

					inner = inner.InnerException;
				}

				return false;
			}
		}

		#endregion

		#region Operators and Overloads

		/// <include file='comments.xml' path='Docs/TestMethodRecord/operatorPlus/*'/>
		[EditorBrowsable(EditorBrowsableState.Never)]
		public static TestMethodRecord operator +(TestMethodRecord t1, TestMethodRecord t2)
		{
			TestMethodRecord ret = new TestMethodRecord();

			ret.testObjects = t1.testObjects;
			ret.testObjects.AddRange( t2.testObjects );
			ret.successes = t1.successes + t2.successes;
			ret.hasRun = t1.hasRun || t2.hasRun;
			return ret;
		}

		/// <include file='comments.xml' path='Docs/TestMethodRecord/ToString/*'/>
		public override string ToString()
		{
			return "Successes=" + Successes.ToString() +
			       " Failures=" + Failures.ToString() +
			       " Total=" + TestCount.ToString();
		}

		#endregion

		/// <include file='comments.xml' path='Docs/TestMethodRecord/RunTest_bool/*'/>
		public bool RunTest(bool assertion)
		{
			return RunTest( assertion, null );
		}

		/// <include file='comments.xml' path='Docs/ITest/Is/*'/>
		public Is Is
		{
			get { return testExpressions; }
		}

		/// <include file='comments.xml' path='Docs/ITest/Are/*'/>
		public Are Are
		{
			get { return testExpressions; }
		}

		public static TestMethodRecord Current {get;set;}
	}
}